<?php
// +----------------------------------------------------------------------
// | KITEGO-Admin「开箱即用」「人人全栈」
// +----------------------------------------------------------------------
// | Copyright (c) 2016~2024 https://www.kitego.cn All rights reserved.
// +----------------------------------------------------------------------
// | Licensed KITEGO并不是自由软件，未经许可不能去掉KITEGO相关版权
// +----------------------------------------------------------------------
// | Author: KITEGO Team <bd@kitego.cn>
// +----------------------------------------------------------------------

namespace kitego\extend\login\admin;

use kitego\enum\ApiCodeEnum;
use kitego\services\common\CacheService;

class AdminLoginBase implements AdminLoginInterface
{
    private $jwtKey; // JWT密钥

    private $accessTtl; // Access Token有效期（秒）

    private $refreshTtl; // Refresh Token有效期（秒）

    private $redisPrefix = 'jwt_refresh:'; // Redis前缀

    public function __construct()
    {
        $this->jwtKey = config('adminjwt.secret_key');
        $this->accessTtl = config('adminjwt.access_ttl');
        $this->refreshTtl = config('adminjwt.refresh_ttl');
    }

    public function login($params)
    {
    }

    /**
     * 生成Token
     */
    public function generateJwtToken($systemAdmin)
    {
        // 角色权限 // 超管、管理员、员工
        $adminRole = ['super', 'admin', 'staff'];

        $deviceId = $this->getDeviceId(); // 设备指纹

        // 生成Tokens
        $tokens = $this->generateTokens($adminRole, $systemAdmin, $deviceId);

        return success([
            'adminToken' => $tokens,
            'adminInfo' => [
                'adminRole' => $adminRole,
                'adminId' => $systemAdmin['id'],
                'adminNickname' => $systemAdmin['nickname'],
                'adminAvatar' => $systemAdmin['avatar'],
            ]
        ]);
    }

    /**
     * 刷新Token
     */
    public function refreshToken($refreshToken)
    {
        // 验证Refresh Token
        $decoded = getAdminJWT($refreshToken);

        // 检查Redis中是否存在且未使用
        $redisKey = $this->redisPrefix . $decoded->jti;
        $storedToken = CacheService::get($redisKey);

        if (!$storedToken || $storedToken['used']) exception('刷新Token无效或已使用');

        // 验证设备指纹
        $deviceId = $this->getDeviceId();
        if ($storedToken['device_id'] !== $deviceId) exception('设备不匹配');

        // 标记旧Token为已使用
        CacheService::set($redisKey, [
            'user_id' => $storedToken['user_id'],
            'device_id' => $storedToken['device_id'],
            'used' => true
        ], $this->accessTtl); // 保留短时间用于防止重放攻击

        // 生成新的Tokens
        $adminRole = $decoded->adminRole;
        $systemAdmin = [
            'id' => $decoded->adminId,
            'nickname' => $decoded->adminNickname,
            'avatar' => $decoded->adminAvatar,
        ];
        $tokens = $this->generateTokens($adminRole, $systemAdmin, $deviceId);

        return success($tokens);
    }

    /**
     * 退出登录
     */
    public function logout()
    {
        $authorization = request()->header('Authorization');
        if (empty($authorization)) exception('未授权');

        list(, $accessToken) = explode(' ', $authorization);

        // 验证AccessToken
        $decoded = getAdminJWT($accessToken);

        // 将AccessToken添加到黑名单
        $blacklistKey = 'jwt_blacklist:' . $decoded->jti;
        CacheService::set($blacklistKey, true, $decoded->exp - time());

        // 使RefreshToken失效
        $refreshTokenId = $decoded->refresh_jti;
        $redisKey = $this->redisPrefix . $refreshTokenId;
        CacheService::delete($redisKey);

        return success('退出登录成功');
    }

    /**
     * 获取设备指纹
     */
    private function getDeviceId()
    {
        $userAgent = request()->header('User-Agent');
        $ip = request()->ip();

        return md5($userAgent . $ip . $this->jwtKey);
    }

    /**
     * 生成AccessToken和RefreshToken
     */
    private function generateTokens($adminRole, $systemAdmin, $deviceId)
    {
        $currentTime = time();

        // 生成AccessToken
        $accessPayload = [
            'iss' => request()->host(), // 令牌的签发者（Issuer），通常是应用或服务的域名
            'sub' => 'access_token', // 令牌的主题（Subject），标识令牌的类型或用途
            'aud' => 'user', // 令牌的受众（Audience），即令牌的预期接收者
            'iat' => $currentTime, // 令牌的签发时间（Issued At），以 UNIX 时间戳表示
            'exp' => $currentTime + $this->accessTtl, // 令牌的过期时间（Expiration Time），以 UNIX 时间戳表示
            'jti' => uniqid('access_'), // 令牌的唯一标识符（JWT ID），通常是随机字符串
            'refresh_jti' => uniqid('refresh_'), // 关联的RefreshToken ID

            'adminRole' => $adminRole,
            'adminId' => $systemAdmin['id'],
            'adminNickname' => $systemAdmin['nickname'],
            'adminAvatar' => $systemAdmin['avatar'],
        ];

        $accessToken = setAdminJWT($accessPayload);

        // 生成RefreshToken
        $refreshPayload = [
            'iss' => request()->host(),
            'sub' => 'refresh_token',
            'aud' => 'user',
            'iat' => $currentTime,
            'exp' => $currentTime + $this->refreshTtl,
            'jti' => $accessPayload['refresh_jti'], // 使用与AccessToken关联的ID

            'adminRole' => $adminRole,
            'adminId' => $systemAdmin['id'],
            'adminNickname' => $systemAdmin['nickname'],
            'adminAvatar' => $systemAdmin['avatar']
        ];

        $refreshToken = setAdminJWT($refreshPayload);

        // 存储RefreshToken到Redis
        $redisKey = $this->redisPrefix . $refreshPayload['jti'];
        CacheService::set($redisKey, [
            'user_id' => $systemAdmin['id'],
            'device_id' => $deviceId,
            'used' => false
        ], $this->refreshTtl);

        return [
            'access_token' => $accessToken,
            'refresh_token' => $refreshToken,
            'expires_in' => $this->accessTtl
        ];
    }

    /**
     * 检查Token是否在黑名单中
     */
    public function checkBlacklist($token)
    {
        // 验证Token
        $decoded = getAdminJWT($token);

        // 检查是否在黑名单中
        $blacklistKey = 'jwt_blacklist:' . $decoded->jti;
        if (CacheService::has($blacklistKey)) abort(ApiCodeEnum::UNAUTHORIZED, 'Token已失效');

        return $decoded;
    }
}